package br.ufrgs.dbproject.dsl.sqlbatch;

import java.security.InvalidParameterException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;

import br.ufrgs.dbproject.database.DatabaseConnector;

public class SQLCommandsBatch implements SqlInsertionBatch, ColumnsSelection, ValuesSelection {

	private Statement batchStatement;

	private String selectedTableName;
	private String[] selectedColumns;
	private String[] selectedValues;
	
	/**
	 * Default maximum batch size.
	 * 
	 * <p>
	 * When batch size reach 10 it'll be automatically executed.
	 * </p>
	 */
	private int maximumBatchSize = 2000;
	
	/**
	 * Cached commands count
	 */
	private int batchSize = 0;

	/**
	 * Creates a {@link SQLCommandsBatch} for the provided {@link Connection}
	 * and initializes a {@link Statement} to be used as a SQL commands batch.
	 * 
	 * @param connection a JDBC connection
	 */
	private SQLCommandsBatch(Connection connection) {
		
		try {
			batchStatement = connection.createStatement();
			
		} catch (SQLException e) {
			throw new RuntimeException("Could not create JDBC statement.", e);
		}
	}
	
	/**
	 * Method Factory.
	 * 
	 * <p>
	 * Creates an insertion batch for the provided database parameters
	 * </p>
	 * 
	 * @param host
	 *            the database host in the form:
	 *            jdbc:postgresql://host/data_base_name
	 * @param user
	 *            database username
	 * @param pass
	 *            database password
	 * 
	 * @return an instance of {@link SqlInsertionBatch}
	 */
	public static SqlInsertionBatch createInsertionBatchFor(String host, String user, String pass) {
		
		Connection connection = DatabaseConnector.createDatabaseConnection(host, user, pass);
		return new SQLCommandsBatch(connection);
	}
	
	/**
	 * Method Factory.
	 * 
	 * <p>
	 * Creates an insertion batch for the provided specific connection
	 * </p>
	 * 
	 * @param connection the database connection used to execute batch
	 * 
	 * @return ab instance of {@link SqlInsertionBatch}
	 */
	public static SqlInsertionBatch createInsertionBatchFor(Connection connection) {
		return new SQLCommandsBatch(connection);
	}

	/**
	 * Executes the a commands batch with the created commands until the execution time
	 */
	public int[] execute() throws SQLException {
		
		if (batchSize > 0) {
			int[] result = batchStatement.executeBatch();
			batchSize = 0;
			return result;
		}
		
		/*
		 * if (batchSize > 0) {
			BatchExecutorService.execute(batchStatement);
			batchSize = 0;
		}
		 */
		
		return null;
	}

	/**
	 * Selects the table to be used by the command being created
	 */
	public ColumnsSelection forTable(String tableName) {

		if (tableName == null || tableName.isEmpty()) {
			throw new InvalidParameterException("forTable expects a not null neither empty table name");
		}

		selectedTableName = tableName;
		return this;
	}

	/**
	 * Selects the columns of the previous selected table to receive data
	 */
	public ValuesSelection insertIntoColumns(String... columns) {

		if (columns == null || columns.length == 0) {
			throw new InvalidParameterException("You must provide at least one column name.");
		}

		if (Arrays.binarySearch(columns, "") >= 0) {
			throw new InvalidParameterException("Null or empty column names are not allowed.");
		}

		this.selectedColumns = columns;
		return this;
	}

	/**
	 * Selects the values that wil be inserted into the previous selected columns
	 * 
	 * <p>
	 * The values are ordered by the order of the previous selected columns
	 * </p>
	 */
	public SqlInsertionBatch theseValues(String... values) {

		
		try {
			this.selectedValues = values;
			
			batchStatement.addBatch(InsertClauseCreator.create(selectedTableName, selectedColumns, selectedValues));
			batchSize++;
			
			if (batchSize >= maximumBatchSize) {
				execute();
			}
			
			return this;
			
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}

	public String getSelectedTable() {
		return selectedTableName;
	}

	public String[] getSelectedColumns() {
		return selectedColumns;
	}

	public Object[] getSelectedValues() {
		return selectedValues;
	}

	public int getMaximumBatchSize() {
		return maximumBatchSize;
	}

	public void setMaximumBatchSize(int maximumBatchSize) {
		this.maximumBatchSize = maximumBatchSize;
	}

	public int getBatchSize() {
		return batchSize;
	}
}
